home *** CD-ROM | disk | FTP | other *** search
/ LSD Docs / LSD Docs.iso / FILEZ / lsd15.dms / lsd15.adf / Ctutorial2.doc.pp / Ctutorial2.doc
Text File  |  1991-02-19  |  31KB  |  613 lines

  1.           ANOTHER C TUTURIAL (WHERE DO THEY ALL COME FROM?)
  2.  
  3. By Joseph M. Hinkle.
  4.  
  5.  
  6.         This is raw text prepared for articles and a book on programming the
  7. Amiga for novices with the intent of quickly getting the reader into the
  8. more powerful features fairly quickly.  There are some errors of
  9. explanation of more abstruse things which I need to clean up, but in the
  10. main() (Ha! a little c joke) you will find this useful.  Because it is a
  11. kind of super-outline of the book it moves along at a pace you may find
  12. breathtaking if you are new to programming the machine, but careful study
  13. of this and the ROM Kernel Manuals will help you greatly.
  14.         The tutorial explains lists, tasks, and startup code with comments on
  15. programming style and example programs which I have tested thoroughly.  The
  16. discussion and examples are valid for Workbench v1.2 (v1.3 is just becoming
  17. available as I post this, so I haven't seen anything on it yet, but I doubt
  18. there would be much trouble using that version).  I use the Lattice v4.01
  19. compiler and don't have Manx, so can't say much about compiling with that
  20. one.  Expect some minor trouble, but the examples are Amiga specific, not
  21. compiler specific, for the most part.
  22.         I hang around A-Link in Everett, Washington at (206) 774-4735, run by
  23. the charming and gracious John (He paid me to say this) Willott, and I go
  24. by the name Marty Hinkle on the board.  Please send me questions and
  25. comments there.
  26.         I also have a tutorial on making shared libraries yourself without using
  27. assembly code (other than the skeleton in the RKM) which does not require
  28. you to be proficient in assembly coding.  If someone is interested in that,
  29. I'll straighten it up and upload it.
  30. This production comes from extensive study of the machine itself with
  31. little input from Commodore (It is probably quite shortsighted of me to not
  32. e in their developer's program).  A shareware style donation will not be
  33. inappropriate, and you will get a free copy of the whole book if I can ever
  34. get a publisher.
  35.  
  36.                                                   Joseph M. Hinkle
  37.                                                   Route 2, Box 2647
  38.                                                   Lopez, Washington 98261
  39.  
  40. PROGRAMMING THE AMIGA - FROM NOVICE TO ADVANCED
  41.         Anyone getting the idea to write a useful program on the Amiga decides
  42. to get the Rom Kernel Manual and DOS manual to learn how the system works.
  43. The size of the manuals gives one pause.  Reading them is daunting.  After
  44. a bit of study one realizes there is no good overview of the executive and
  45. disk operating system, but there is plenty of detail.  Trying out some of
  46. the example programs can lead to odd results as there are many small
  47. mistakes in them which one can overlook.  After getting something to work
  48. many questions are left, such as "What will make this program work under
  49. Workbench?", "How can this program be made to multitask?", "What is a
  50. process?", and "How can this program be loaded by another?".  All of these
  51. are mentioned in the books and there is much discussion of them, but
  52. nowhere are there clear examples of useful programs that implement these
  53. things.  You will find, after some experience with the machine, that nearly
  54. every point is mentioned somewhere in the books.  Tying them all together
  55. is the difficult part.
  56.         I will cover these questions and more by showing a simple program to
  57. establish a style, then using that program to build ever more involved
  58. programs which will take you through the heart of the Amiga.  I will refer
  59. to the RKM (ROM Kernel Manual, Addison-Wesley, in four volumes: ROM Kernel
  60. Reference Manual: Libraries and Devices,  ROM Kernel Reference
  61. Manual:Exec, Intuition Reference Manual, and Hardware Reference Manual),
  62. and the AmigaDOS Manual, Bantam Books.  These might appear pricey, but for
  63. serious programming on the Amiga they are indispensable.  If you are new
  64. to the c programming language you should also have a text on c such as the
  65. original language definition "The c Programming Language" by Kernighan and
  66. Ritchie, Prentice-Hall, or another textbook on the subject.  Make sure you
  67. understand data structures because there will be a lot of references to
  68. them.  Let's get started:
  69.         We will need a timer for this experiment, something that goes tick,
  70. tick, tick, every second or so.  There is a section in the RKM: Libraries
  71. and Devices called Timer Device. To see if everything will work as
  72. advertised, let's try a simple delay.  We need to open a timer, run it,
  73. and then close it.  In order to open a timer we need to fill in a
  74. timerequest structure (RKM:Libraries and Devices, Include Files,
  75. devices/timers.h.  Note the .i structures are for assembly language
  76. writers).  Note the structure is tagged timerequest, not timeRequest as in
  77. the text.  The structure is:
  78.  
  79. struct timerequest {
  80.         struct IORequest tr_node;
  81.         struct timeval  tr_time;
  82. }
  83.  
  84. Aha! A typical Amiga data structure!  It consists of nothing more than
  85. other structures!  Sigh.  There is hope, though.  The struct timeval is in
  86. the same include file:
  87.  
  88. struct timeval {
  89.         ULONG tv_secs;
  90.         ULONG tv_micro;
  91. }
  92.  
  93. But what is a ULONG?  Look in RKM:Exec, Include Files, exec/types.h:
  94.  
  95. typedef unsigned long ULONG;
  96.  
  97. Now we know the timerequest structure is a template for an IORequest
  98. structure and two 32 bit quantities.  At the top of the file timer.h there
  99. is a line:
  100.  
  101. #include exec/io.h
  102.  
  103. So, while you're thumbing through RKM:Exec, look at exec/io.h.  You will
  104. find:
  105.  
  106. struct IORequest {
  107.         struct Message  io_Message;
  108.         struct Device   *io_Device;
  109.         struct Unit             *io_Unit;
  110.         UWORD   io_Command;
  111.         UBYTE   io_Flags;
  112.         BYTE    io_Error;
  113. }
  114.  
  115. That file #includes exec/ports.h, which defines the struct Message and
  116. struct MsgPort.  That file in turn references exec/nodes.h, exec/lists.h
  117. and exec/tasks.h, which contain the definitions needed to completely
  118. specify the struct timerequest (In my compiler's include files there is
  119. no mention of #including exec/types.h, so I have to do that myself.
  120. #including devices/timer.h gets all the rest).  The reason for belaboring
  121. all the various data structures is to show you what happens when a request
  122. is made, that is, just what data is passed around the machine and what
  123. Exec does with it.  Exec, the executive program, supervises the use of the
  124. microprocessor, responding to interrupts and checking tables of things to
  125. do.  It is generally invisible to you but it helps to know it is there,
  126. and we will be using its library of functions often.
  127.         This exercise also gets you used to referencing the RKM, which you will
  128. also be doing often.  Continuing with the full definition of struct
  129. timerequest,  we see it is a template for memory arranged like this,
  130. assuming we call the structure TR:
  131.  
  132. LONG TR.tr_node.io_Message.mn_Node.ln_Succ ; points to the following node
  133. LONG TR.tr_node.io_Message.mn_Node.ln_Pred ; points to the preceding node
  134. BYTE TR.tr_node.io_Message.mn_Node.ln_Type ; defines the type of node
  135. BYTE TR.tr_node.io_Message.mn_Node.ln_Pri  ; this node's priority
  136. LONG TR.tr_node.io_Message.mn_Node.ln_Name ; points to a text string name
  137. LONG TR.tr_node.io_Message.mn_ReplyPort    ; points to a message port
  138. WORD TR.tr_node.io_Message.mn_Length       ; length of this whole block
  139. LONG TR.tr_node.io_Device                  ; points to a struct Device
  140. LONG TR.tr_node.io_Unit                    ; points to a struct Unit
  141. WORD TR.tr_node.io_Command                 ; a command
  142. BYTE TR.tr_node.io_Flags                   ; flags set
  143. BYTE TR.tr_node.io_Error                   ; error returned
  144. LONG TR.tr_time.tv_secs                    ; seconds requested
  145. LONG TR.tr_time.tv_micro                   ; microseconds requested
  146.  
  147.         These 40 bytes are the complete timerequest structure.  We fill in some
  148. members ourselves and functions provided in the system fill in others.  In
  149. particular, we need to fill in what kind of timer it is, how we want the
  150. timer to behave (the command), and when we want the timer to reply to us.
  151.         First, however, we have to allocate some memory for the structure.
  152. There is a routine available which will do that for us, CreateExtIO(), but
  153. one of the things it needs is the address of a message port (struct
  154. MsgPort), which, as you recall from looking in exec/ports.h, is a little
  155. structure itself, starting with a Node structure.  Are you getting the
  156. idea that everything starts with a Node structure?  Just about everything
  157. does, because Exec uses these to link them into Lists (RKM:Exec, Lists)
  158. which are scanned periodically to see if anything needs doing, or when a
  159. routine wants to see if it has any mail.  Whole data structures are not
  160. passed around in the machine, just addresses of nodes, which also happen
  161. to be the first addresses of most structures.  We will get into detail on
  162. that later; suffice to say that if we ever get this memory allocated we
  163. will ask somebody to link our structure into some list or another so it
  164. will get processed.
  165.         There is a good discussion of memory allocation in RKM:Exec, Memory
  166. Allocation but fortunately there is a routine for that, too, for certain
  167. cases such as we need now.  CreatePort(), which needs only a name and a
  168. priority (RKM:Exec, Tasks, et al), will provide us with the address of a
  169. MsgPort structure we can use to call CreateExtIO(), which will provide us
  170. with an address of a timerequest structure, which in turn we will use to
  171. open a timer device.  For now, we will make everything priority 0.
  172.         These routines should be declared as functions returning something to
  173. make life easier as we write the program.  Some, like CreatePort(), always
  174. return the address of a MsgPort structure, so we can declare them
  175. globally. Others, like CreateExtIO() return the address of differently
  176. sized blocks, depending on what we are doing, so they should be cast as
  177. the type we need as we call them.
  178.         We also have to remember to free up the memory when we are done, and
  179. the routines DeletePort() and DeleteExtIO() will do that for us.  After
  180. all this discussion, the problem is again beginning to look simpler.  We
  181. will eventually fix things so they will get simpler yet.  These routines,
  182. by the way, are described in RKM:Libraries and Devices, Library Summaries.
  183.         So here it is:
  184.  
  185. /***** Tick.c ***********************************************************/
  186.  
  187. #include "exec/types.h"
  188. #include "devices/timer.h"
  189.  
  190. struct MsgPort *CreatePort();
  191.  
  192. void
  193. main()
  194. {
  195.         struct MsgPort *TP;
  196.         struct timerequest *TR;
  197.         int error;
  198.  
  199.         TP = CreatePort(NULL, 0);
  200.         if (TP == NULL) {
  201.                 printf("Not enough memory for the Message Port\n");
  202.                 exit(0);
  203.         }
  204.  
  205.         TR = (struct timerequest *)
  206.                                         CreateExtIO(TP, sizeof(struct timerequest));
  207.         if (TR == NULL) {
  208.                 printf("Not enough memory for the timerequest\n");
  209.                 DeletePort(TP);
  210.                 exit(0);
  211.         }
  212.  
  213.         error = OpenDevice("timer.device", UNIT_VBLANK, TR, 0);
  214.         if (error > 0) {
  215.                 printf("The timer won't open\n");
  216.                 DeleteExtIO(TR, sizeof(struct timerequest));
  217.                 DeletePort(TP);
  218.                 exit(0);
  219.         }
  220.  
  221.         TR->tr_node.io_Command = TR_ADDREQUEST;
  222.         TR->tr_time.tv_secs = 5;
  223.         TR->tr_time.tv_micro = 0;
  224.  
  225.         DoIO(TR);
  226.  
  227.         printf("Tick\n");
  228.  
  229.         CloseDevice(TR);
  230.         DeleteExtIO(TR, sizeof(struct timerequest));
  231.         DeletePort(TP);
  232.  
  233. }
  234.  
  235. /***********************************************************************/
  236.  
  237.         You did read the section RKM: Timer Device didn't you?  So you know
  238. what a UNIT_VBLANK is, and that the constants in capital letters are
  239. defined in the appropriate include files.  I also included error checks
  240. in a crude way for good programming practice.  Compile this and play with
  241. it a minute or two.
  242.         All we have done here is allocate memory for the two structures that we
  243. use, initialized them with our values, requested the system to link them
  244. into the appropriate lists, and waited.  Five seconds later, our routine
  245. wakes up, prints a "Tick" message, and exits.  If you don't believe that,
  246. change the value of TR->tr_time.tv_secs to about ten or fifteen, recompile
  247. it, and RUN Tick.  A new CLI will be created, and for the time you have
  248. called for you will be able to perform a DOS command like Date in your
  249. original CLI.  In the time you have specified, the "Tick" message will
  250. appear and the background CLI will end.  You've been multitasking!  The
  251. trouble is that the manuals don't clearly show how you can accomplish
  252. that from within a program.
  253.         You have read the RKM: Exec, Input/Output so you saw the mention of
  254. DoIO() and the other functions for doing IO.  In the program we tried, the
  255. function DoIO() was used to make things simple.  We built the appropriate
  256. structures and DoIO() requested the timerequest structure to be linked
  257. into a message port somewhere.  Did you notice in your earlier perusal of
  258. the exec/ports.h file that the last item in a struct MsgPort is a struct
  259. List?  Not a pointer to a structure, but an actual structure.  The ln_Pred
  260. and ln_Succ fields of your timerequest structure are changed by Exec to
  261. point to the proper elements of that list.  When your time is up Exec
  262. changes the pointers to link the message (timerequest structure) onto the
  263. MsgPort you called for in CreatePort().  DoIO() sees there is mail, sets
  264. any error codes or flags, and exits.  We will be doing quite a bit with
  265. this timer program, so it would be better if we had more convenient
  266. routines to handle allocation and deallocation for us, as well as setting
  267. and stopping a timer.  So, before we continue, let's build a file of timer
  268. utilities to help us.  You might want to modify these somewhat to suit your
  269. specific needs.  In particular, SetTimer() does not now set the
  270. microseconds part of the timerequest so only even second periods are
  271. available.  If you wanted, you could change the formal variable to a double
  272. and do the math to get microseconds right in the routine.
  273.         An important note about compilers:  The stack checking routines
  274. various compilers put into the code will not work in task code.  Since we
  275. expect to be using these routines within tasks, they and any of the following
  276. routines which will become part of task code must be compiled with stack
  277. checking disabled.  There is a further consideration if you are using Lattice
  278. v4.0: When task code is added to the system task list, register A4 is not
  279. preserved, thereby destroying the code's reference to its data sections.
  280. There is a compiler option to cause it to generate register saving code at
  281. the beginning of each function call.  It costs only 24 bytes per function,
  282. and is necessary only for functions referenced by AddTask() or
  283. CreateTask() (q.v.).  It's unlikely any of these would be, but you never
  284. know.  For Lattice 4.0 the compiler command line would be:
  285.  
  286.         lc -v -y TimerUtilities
  287.  
  288.         Carefully check your compiler documentation for any special treatment
  289. required for task code.
  290.  
  291. /***** TimerUtilities.c *************************************************/
  292. /*                                                                                                                               */
  293. /*              A package of utilities to control timer devices.        /*
  294. /*
  295. /*              This package contains:                                 /*                                                         */
  296. /*                                                                                                                               */
  297. /*              CreateTimer(unit, priority)                                                                                */
  298. /*                      returns: Pointer to a struct timerequest or zero if trouble             */
  299. /*              AbortTimer(timerequest)                                                                                     */
  300. /*                      returns: Nothing                                                                                      */
  301. /*              SetTimer(time)                                                                                                */
  302. /*                      returns: Nothing                                                                                      */
  303. /*              DeleteTimer(timerequest)                                                                                    */
  304. /*                      returns: Nothing                                                                                      */
  305. /*                                                                                                                               */
  306. /************************************************************************/
  307.  
  308. #include "exec/types.h"
  309. #include "devices/timer.h"
  310.  
  311. extern struct MsgPort * CreatePort();
  312.  
  313. struct timerequest *
  314. CreateTimer(unit, priority)
  315.         ULONG unit;
  316.         LONG priority;
  317. {
  318.         struct MsgPort *TP;
  319.         struct timerequest *TR;
  320.  
  321.         if ((TP = CreatePort(NULL, priority)) == NULL)
  322.                 return(NULL);
  323.  
  324.         if ((TR = (struct timerequest *)
  325.                         CreateExtIO(TP, sizeof(struct timerequest))) == NULL) {
  326.                 DeletePort(TP);
  327.                 return(NULL);
  328.         }
  329.  
  330.         if (OpenDevice(TIMERNAME, unit, TR, 0) != NULL) {
  331.                 DeleteExtIO(TR, sizeof(struct timerequest));
  332.                 DeletePort(TP);
  333.                 return(NULL);
  334.         }
  335.  
  336.         return(TR);
  337. }
  338.  
  339. void
  340. AbortTimer(T)
  341.         struct timerequest *T;
  342. {
  343.         if (AbortIO(T) == NULL) {
  344.                 Wait(1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit);
  345.                 GetMsg(T->tr_node.io_Message.mn_ReplyPort);
  346.         }
  347. }
  348.  
  349. void
  350. SetTimer(T, time)
  351.         struct timerequest *T;
  352.         int time;
  353. {
  354.                 AbortTimer(T);
  355.                 T->tr_node.io_Command = TR_ADDREQUEST;
  356.                 T->tr_time.tv_secs = time;
  357.                 T->tr_time.tv_micro = 0;
  358.                 SendIO(T);
  359. }
  360.  
  361. void
  362. DeleteTimer(TR)
  363.         struct timerequest *TR;
  364. {
  365.         struct MsgPort *TP;
  366.  
  367.         if (TR != 0) {
  368.                 AbortTimer(TR);
  369.                 TP = TR->tr_node.io_Message.mn_ReplyPort;
  370.                 CloseDevice(TR);
  371.                 DeleteExtIO(TR, sizeof(struct timerequest));
  372.                 DeletePort(TP);
  373.         }
  374. }
  375.  
  376. /************************************************************************/
  377.  
  378.         Compile these, but don't link them.  There's nothing to link to yet.
  379. Notice the routines don't use DoIO().  They use SendIO() instead.  That
  380. will allow us to use them in a task we will create Real Soon Now.  First,
  381. let's write a test routine to see if everything works.  We'll include an
  382. argument this time so we will be able to set the timer to anything we
  383. want.  We'll use the function WaitPort() to wait for completion of the
  384. timer.  This gets us closer to a full multitasking program.  The reason it
  385. isn't full multitasking is that we aren't doing anything else while
  386. waiting for a message (the timerequest structure) to arrive at our message
  387. port.
  388.  
  389. /***** Test.c ***********************************************************/
  390.  
  391. #include "exec/types.h"
  392. #include "devices/timer.h"
  393.  
  394. struct timerequest *CreateTimer();
  395.  
  396. void
  397. main(argc, argv)
  398.         int argc;
  399.         char *argv[];
  400. {
  401.         struct timerequest *T;
  402.         struct MsgPort *P;
  403.         int delay;
  404.  
  405.         if (argc != 2) {
  406.                 printf("Please provide a delay time\n");
  407.                 exit(0);
  408.         }
  409.  
  410.         delay = atoi(argv[1]);
  411.  
  412.         T = CreateTimer(UNIT_VBLANK, 0);
  413.         P = T->tr_node.io_Message.mn_ReplyPort;
  414.         SetTimer(T, delay);
  415.         WaitPort(P);
  416.         printf("Tock\n");
  417.         DeleteTimer(T);
  418. }
  419.  
  420. /************************************************************************/
  421.  
  422.         Compile this and link it with TimerUtilities.o to get a complete
  423. program.  Alternatively, you could combine both files together just for
  424. test purposes.  Now run the test program, remembering to include a delay
  425. value: Test 3, for example.
  426.         Allright! The utility package is quite a help.  Now, look carefully at
  427. AbortTimer().  A brief mention should be made here about the function
  428. AbortIO().  It isn't described in some books.  If the IO has completed
  429. when AbortIO() is called, it returns a -1 from a timer device (or  a
  430. meaningless value from some other devices), and no message is attached to
  431. the message port.  If the IO has not completed and is aborted, the
  432. function returns a 0 and the timerequest structure (our message) is
  433. attached to the message port.  We then call GetMsg() to remove it from the
  434. port, something unnecessary in this application, but possibly required in
  435. others that use this routine.  We will see more of GetMsg() later.  The
  436. way AbortTimer() waits for completion is Wait().  This function waits for
  437. a particular bit to be set.  While waiting, of course, just as in the case
  438. of WaitPort() and WaitIO(), Exec can be doing other things.  To see that
  439. effect, try Running several copies of Test at the same time: Run Test 30
  440. <CR> Run Test 20 <CR> Run Test 10 <CR>.  You should see the word "Tock"
  441. printed three times, ten seconds apart.
  442.         I said Wait() waits for a particular bit to be set.  This bit is
  443. called a signal, and every message port gets one allocated.  They have to be
  444. allocated because for every task, like the one we are running when we run
  445. Test, has a longword (32 bits) in a structure available for signalling.
  446. Each bit has to have a unique meaning.  Exec takes the lower sixteen for
  447. itself, leaving the upper sixteen available to us.  We don't know which
  448. bits have been allocated already, or to what, so we use a routine
  449. AllocSignal() to find out our bit number.  The signal must be freed with
  450. FreeSignal() when we don't need it any more.  These details have been
  451. taken care of for us by CreatePort() and DeletePort().  The point is that
  452. there is a number in a message port that is the number of a bit (from 0 to
  453. 31) that says when this bit is set in a certain location, mail has arrived
  454. at the port.  It stays set until you Wait() for it in some fashion or
  455. another ( WaitPort() or WaitIO() ).  We can recreate that bit by shifting
  456. a 1 left the number of times equal to the signal number.  The signal
  457. number is in a struct MsgPort, element mp_SigBit.  Remembering that in our
  458. timerequest structure there is an IORequest structure containing a
  459. Message structure which contains a pointer to a message port, the
  460. reference is mn_ReplyPort->mp_SigBit.  Since that Message structure is
  461. part of an IORequest structure we have to say
  462. io_Message.mn_ReplyPort.mp_SigBit.  That IORequest in turn is a named part
  463. of a timerequest structure called tr_node.  So, we obtain the number,
  464. shift a 1 left that number of times, and Wait() for it to be set, which
  465. yields the mouthful operation:
  466.  
  467. Wait(1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit);
  468.  
  469.         We won't say that very often, but we will be using Wait() on specific
  470. bits.
  471.         Let's review what we've done.  We allocated and initialized a MsgPort
  472. structure.  That sits off to the side as our mailbox.  We allocated and
  473. initialized a timerequest structure, among other things putting the
  474. address of our mailbox in it so the system will know where to send the
  475. structure back to when it's done with it, a return address, if you like.
  476. That's our message.  We put some stuff in the message (the command and
  477. time values), sent it off to Exec, and waited for mail to arrive at our
  478. mailbox.  Because of the particular functions used for waiting, the
  479. microprocessor can go to sleep if Exec isn't doing anything else.  We
  480. could have done busy loop waiting, which consists of looking at some value
  481. in the MsgPort to see if it has changed, and if it hasn't, looking again.
  482. That kind of program won't quite choke up the machine, depending on the
  483. priority it has, but it eats up all the spare processor time and can make
  484. some operations very slow.  We will demonstrate that with our timer
  485. routines by and by.
  486.         We have been operating as a task spawned by the CLI (A process,
  487. actually, but that is a sort of super task run by DOS.  More on that
  488. later).  We communicated with the rest of the system by sending messages
  489. and looking for signals.  You cannot write a function and call it with
  490. values as in ordinary c programming and have it be a separate task.  You
  491. must use the message passing system.  Exec, whose name we have been
  492. invoking all along, is primarily a program which manipulates lists, lists
  493. of messages being a large part of a busy program.  It is properly called
  494. the executive.  Every time a clock interrupt occurs, Exec is activated,
  495. and it goes looking for things to do by scanning various lists.  You
  496. don't ever call Exec, you just readdress a message.  Exec will know about
  497. it soon enough and take suitable action.
  498.         Let's examine lists more closely (You have been diving into the RKM
  499. to look things up all along, haven't you?  See RKM: Exec, Lists).  A list is
  500. a simple little structure of three pointers and a byte defining its type:
  501.  
  502. LONG lh_Head            ; points to the first node in the list
  503. LONG lh_Tail            ; is always zero
  504. LONG lh_TailPred        ; points to the last node in the list
  505. BYTE lh_Type            ; defines the types of nodes in the list
  506. BYTE lh_pad                     ; is here to make the structure an even number of bytes
  507.  
  508.         The lh_Type field signifies what kind of a list it is.  Those
  509. definitions are in the include file exec/nodes.h, and can be tasks,
  510. interrupts, memory, all the things that Exec keeps track of.
  511.         If the list is empty the lh_Head element points to its lh_Tail and the
  512. lh_TailPred points to its lh_Head.
  513.  
  514.         ---- lh_Head <------
  515.         |                  |
  516.         ---> lh_Tail = 0   |
  517.                            |
  518.              lh_TailPred ---
  519.  
  520.                                 Figure 1
  521.  
  522.         Now if a node is added to the list, the list's head is made to point
  523. to the node, that is, the first location of the node.  The node has no
  524. successors, so the ln_Succ field is made to point to the list's lh_Tail,
  525. which is always zero so a search routine has a place to stop.  The node's
  526. predecessor is the list, so that field is made to point to the first
  527. location of the list.  As more nodes are added on to the list, the ln_Succ
  528. field is made to point to the first location of the following node and the
  529. following node's ln_Pred field is made to point to the first location of
  530. the preceding node.  That way all nodes are linked forwards and backwards.
  531. A list and the nodes linked into it could be represented like this:
  532.  
  533.                                      Node:
  534.                               -------> ln_Succ -------<-|
  535.                               |   ---- ln_Pred       |  |
  536.               List:           |   |    ln_Type       |  |
  537.                 lh_Head -------<---    ln_Pri        |  |
  538.          (zero) lh_Tail <--------      ln_Name       |  |
  539.                 lh_TailPred --- |                    |  |
  540.                               | |                    |  |
  541.                               | |    Node:           |  |
  542.                               | |      ln_Succ <======<-+--|
  543.                               | |      ln_Pred ------+--|  |
  544.                               | |                    |     |
  545.                               | |                    |     |
  546.                               | |    Node:           |     |
  547.                               |_====>  ln_Succ <-----|     |
  548.                                        ln_Pred ------------|
  549.  
  550.                                                                 Figure 2
  551.  
  552.         A node has a type, just like a list, and generally agrees with the
  553. type of list it is in.  It also has a priority so that Exec can establish the
  554. order in which it will process a node.  There is a name field which can be
  555. a pointer to a character string such as "Initial CLI" to give the
  556. structure an identifier that can be searched for without knowing which
  557. list it is in.
  558.         Nodes by themselves aren't of any use, but they are the beginning
  559. structure of almost every structure in the system.  Recalling the
  560. discussion of messages and the timerequest structure, you can see how
  561. messages can be sent to a message port just by finding where the
  562. lh_TailPred field of the message port points, putting that address in the
  563. ln_Pred of the message, and changing the contents of that address to point
  564. to the first location of the message.  Since it will be the latest message
  565. attached to the port, its ln_Succ field will be filled with the address of
  566. the lh_TailPred field of the message port.  Exec has to change just those
  567. three addresses and three more in the list the message came from, if it
  568. was in one, to send it, no matter how large the message.  You don't have
  569. to bother yourself with these details as there are routines that perform
  570. these functions, allowing you to think of "sending" or "putting" a message
  571. somewhere and waiting for a "reply".  The list structure comes more
  572. readily to mind when we link something into one, such as a task (See RKM:
  573. Exec, Tasks and include/exec/tasks.h).
  574.         Tasks are jobs that Exec performs when the conditions warrant, such
  575. as a timer running out, or a key being pressed.  There are certain
  576. limitations to tasks as such.  They are procedures which cannot be called
  577. by another function (Exec does that when the time is right, and it is best
  578. not to interfere with the opertion of Exec.  It bytes).  A task cannot
  579. return a result, and as you shall see, should probably not return at all.
  580. A task cannot call any DOS related input or output functions that require
  581. multitasking like printf(), although way down the line I'll give some
  582. pointers on accomplishing those functions.
  583.   Communication with a task is done by message passing.  There are
  584. standard system messages like we have been using (the timerequest
  585. structure), and you can construct messages to suit your taste as long as
  586. Exec understands them (the beginning structure is a Message structure) and
  587. your task understands them (the remainder of the structure contains
  588. information meaningful to it).  There is a special kind of message called
  589. a semaphore, used to provide a means of mutual exclusion, and
  590. communication is also done by signals, which are single bits of an
  591. unsigned longword. One other means not supported by Exec is by global
  592. variables.  If your task can see a variable changed by another program,
  593. your task can act on it.  However, a very important point for a
  594. multitasking system, you must not busy wait in a task as you will eat up
  595. all the spare time the microprocessor has.  Notice I said spare time and
  596. not necessarily all its time.  That depends on your task's priority.  Busy
  597. waiting is exemplified by this:
  598.  
  599.         s = 0;
  600.         while (s != 999999999) {
  601.                 s = s + 1;
  602.         }
  603.  
  604. Later on we will try a busy waiting routine to see what happens.
  605.         Signals are the simplest executive system for intertask communication.
  606. Every task has available an unsigned longword called tc_SigAlloc.  I'm
  607. sure you have your RKM:Exec book open to the include/exec/tasks section
  608. this very moment, so have a look at the other signal related elememts
  609. tc_SigWait and tc_SigRecvd.  Signals are single bits of the longword
  610. tc_SigAlloc.  As I mentioned earlier in the di^C
  611.  
  612. End.
  613.